# -*- coding:utf-8 -*-
from __future__ import annotations

import datetime
from typing import List

import pandas as pd
import time
from pandas import DataFrame

from core.enums import DateFormat, Period
from domain.transaction_data.repository import trade_cal_repository
from domain.transaction_data.spider import tushare_spider

period_match = {
    '1day': {'unit': 'day', 'num': 1}
}

month_31days = [1, 3, 5, 7, 8, 10, 12]

trading_days_dataFrame = None  # type:DataFrame


def is_trading_day(date=None, dateFormat:DateFormat=DateFormat.Y_m_d, exchange: str = "SSE"):
    """
    判断给定日期是否是股票交易日
    :param date: 如果为None, 则默认为当前日期，格式为%Y-%m-%d 例如 2024-01-03
    :param exchange: 交易所 SSE上交所,SZSE深交所,CFFEX 中金所,SHFE 上期所,CZCE 郑商所,DCE 大商所,INE 上能源
    :return:
    """
    if date is None:
        dt = datetime.datetime.now()
    elif isinstance(date, str):
        dt = datetime.datetime.strptime(date, dateFormat.value)
    elif isinstance(date, datetime.datetime):
        dt = date
    global trading_days_dataFrame
    if trading_days_dataFrame is None:
        closed_days_df = trade_cal_repository.read_closed_cal()
        closed_days_df["date_index"] = pd.to_datetime(closed_days_df["date"])
        closed_days_df.set_index(keys='date_index', inplace=True)
    try:
        closed_days_df.loc[str(dt.year)]
    except:
        raise Exception("请更新股市休市数据")
    if exchange not in closed_days_df.columns:
        raise Exception("请更新股市休市交易所数据")
    try:
        if closed_days_df.loc[dt][exchange] == 1:
            return False
    except:
        pass
    return True


def countTradeDays(begin: datetime.date, end: datetime.date=datetime.date.today()) -> int:
    """
    计算开始和结束日期之间的交易日天数
    :param begin:
    :param end:
    :return:
    """
    count = 0
    delta = datetime.timedelta(days=1)
    start = begin
    while(start <= end):
        dateStr = start.strftime(DateFormat.Y_m_d.value)
        if is_trading_day(dateStr, DateFormat.Y_m_d):
            count = count + 1
        start = start + delta
    return count


def get_lastest_trading_day_by_dateStr(dateStr=None, dateFormat: DateFormat = DateFormat.Y_m_d):
    dateStr = dateStr if dateStr else get_today_dateStr(dateFormat=dateFormat)
    if not is_format_dateStr(dateStr, dateFormat=dateFormat):
        dateStr = formatDateStr(dateStr, sourceFormat=dateFormat, targetFormat=DateFormat.Y_m_d)
    if is_trading_day(dateStr):
        return dateStr
    else:
        while True:
            dateStr = get_dateStr_before_num_days_by_dateStr(1, dateStr, dateFormat=DateFormat.Y_m_d)
            if is_trading_day(dateStr):
                return dateStr


def is_format_dateStr(dateStr, dateFormat : DateFormat = DateFormat.Ymd):
    """
    检测日期字符串是否符合给定的格式
    :param dateStr:
    :return:
    """
    # import re
    # result = re.match(r'^\d{4}-\d{1,2}-\d{1,2}', dateStr)
    # return False if result==None else True
    try:
        datetime.datetime.strptime(dateStr, dateFormat.value)
        return True
    except:
        return False


def change_date_format(dateStr, sourceFormat=DateFormat.Ymd, targetFormat : DateFormat = DateFormat.Y_m_d):
    if is_format_dateStr(dateStr, dateFormat=sourceFormat):
        date = datetime.datetime.strptime(dateStr, sourceFormat.value)
        dateStr = date.strftime(format=targetFormat.value)
        return dateStr
    else:
        return None


def is_exist_date_By_DateStr(dateStr: str, dateFormat:DateFormat=DateFormat.Y_m_d):
    """
    查询给定的日期字符串是否存在
    :param dateStr: 符合dateFormat
    :return:
    """
    dateStr = dateStr if dateStr else get_today_dateStr()
    if not dateStr and not is_trading_day(dateStr):
        return False
    date_p = datetime.datetime.strptime(dateStr, dateFormat.value)
    year, month, day = date_p.year, date_p.month, date_p.day
    current_year = get_current_year()
    if (year < 1980 or year > current_year):
        return False
    month = int(month)
    if (month < 1 or month > 12):
        return False
    day = int(day)
    if (day < 1 or day > 31):
        return False
    current_month = get_current_month()
    current_day = get_current_day()
    if (year == current_year and month == current_month and day > current_day):
        return False
    if (year == current_year and month > current_month):
        return False
    if (month not in month_31days and month != 2 and day > 30):
        return False
    if is_leap_year(year):
        if (month == 2 and day > 28):
            return False
    else:
        if (month == 2 and day > 29):
            return False
    return True


def get_tradingDateStr_before_n_tradingDays(n: int, timestamp: float):
    count = 0
    if not n:
        n = 1
    current_date = today() if not timestamp else get_datetime_from_timestamp(timestamp).date()
    while (count < n):
        current_date = get_date_before_num_days_by_date(1, current_date)
        if (is_trading_day(str(current_date))):
            count += 1
    return str(current_date)


def get_date_before_num_days_by_date(num: int, date):
    current_date = date if date else datetime.date.today()
    return current_date - datetime.timedelta(days=num)


def get_dateStr_before_num_days_by_dateStr(num: int, dateStr: str, dateFormat:DateFormat=DateFormat.Y_m_d):
    dateStr = dateStr if dateStr else get_today_dateStr(dateFormat=dateFormat)
    dateTime = get_date_from_dateStr(dateStr, dateFormat=dateFormat)  # type:datetime.datetime
    dateTime = get_date_before_num_days_by_date(num, dateTime)
    return dateTime.strftime(dateFormat.value)


def getLastestTradingDate(date: str|datetime.datetime = None, dateFormat:DateFormat=DateFormat.Y_m_d) -> datetime.datetime:
    if date is None:
        dt = datetime.datetime.now()
    elif isinstance(date, str):
        dt = datetime.datetime.strptime(date, dateFormat.value)
    elif isinstance(date, datetime.datetime):
        dt = date
    if is_trading_day(dt):
        return dt
    else:
        delta = datetime.timedelta(days=1)
        while True:
            dt = dt - delta
            if is_trading_day(dt):
                return dt


def get_dateStr_from_dateTime(dateTime, dateFormat=DateFormat.Y_m_d_H_M_S):
    return datetime.datetime.strftime(dateTime, dateFormat.value)


def get_date_from_dateStr(dateStr: str, dateFormat:DateFormat=DateFormat.Y_m_d_H_M_S):
    return datetime.datetime.strptime(dateStr, dateFormat.value)


def formatDateStr(date: str, sourceFormat: DateFormat, targetFormat:DateFormat):
    dateTime = datetime.datetime.strptime(date, sourceFormat.value)
    return datetime.datetime.strftime(dateTime, targetFormat.value)


def is_leap_year(year: int):
    return True if year > 0 and year % 4 == 0 else False


def today() -> datetime.date:
    day = datetime.datetime.today().date()
    return day


def get_current_year() -> int:
    """
    获取当前年份
    :return: int
    """
    year = datetime.datetime.today().year
    return year


def get_current_month() -> int:
    month = datetime.datetime.today().month
    return month


def get_current_day() -> int:
    """
    获取当前日期
    :return:
    """
    return datetime.datetime.today().day


def get_current_hour() -> int:
    return datetime.datetime.today().hour


def get_current_quarter():
    current_month = get_current_month()
    if current_month in [1, 2, 3]:
        return 1
    elif current_month in [4, 5, 6]:
        return 2
    elif current_month in [7, 8, 9]:
        return 3
    elif current_month in [10, 11, 12]:
        return 4


def get_now_str(dataFormat=DateFormat.Y_m_d_H_M_S):
    return time.strftime(dataFormat.value)


def get_today_dateStr(dataFormat:DateFormat=DateFormat.Y_m_d):
    return time.strftime(dataFormat.value)


def today_last_year():
    lasty = datetime.datetime.today().date() + datetime.timedelta(-365)
    return str(lasty)


def getLastestTradeday() -> datetime.date:
    """
    获取最后一个交易日
    :return:
    """
    last_trade_day = today()
    oneday = datetime.timedelta(days=1)
    while is_trading_day(last_trade_day.strftime(DateFormat.Y_m_d.value)) == False:
        last_trade_day = last_trade_day - oneday
    return last_trade_day


def getPeriodLastestTradingDatetime(period: Period == Period.day) -> datetime.datetime:
    lastestTradeDay:datetime.date = getLastestTradeday()
    if today() > lastestTradeDay:
        return datetime.datetime.strptime(lastestTradeDay.strftime(DateFormat.Ymd.value) + "150000", DateFormat.YmdHMS.value)
    delta = datetime.timedelta(days=1)
    if period == Period.minute30:
        delta = datetime.timedelta(minutes=30)
    elif period == Period.minute5:
        delta = datetime.timedelta(minutes=5)
    elif period == Period.minute1:
        delta = datetime.timedelta(minutes=1)
    now = datetime.datetime.now()
    hm = now.hour * 100 + now.minute
    if period == Period.day:
        if hm < 930:
            return datetime.datetime.strptime(time.strftime(DateFormat.Ymd.value) + "000000",DateFormat.YmdHMS.value)
        else:
            return now.replace(year=lastestTradeDay.year, month=lastestTradeDay.month, day=lastestTradeDay.day, hour=0, minute=0, second=0)
    else:
        if hm < 930:
            return now.replace(year=lastestTradeDay.year, month=lastestTradeDay.month, day=lastestTradeDay.day, hour=15,
                               minute=0, second=0)
        elif hm == 930:
            return now.replace(hour=10, minute=0, second=0)
        elif hm == 1300:
            return now.replace(hour=13, minute=30, second=0)
        elif 1130 > hm > 930:
            start = now.replace(hour=9, minute=30, second=0)
            hm1 = start.hour * 100 + start.minute
            while hm1 < hm:
                start = start + delta
                hm1 = start.hour * 100 + start.minute
            return start
        elif 1300 < hm < 1500:
            start = now.replace(hour=13, minute=0, second=0)
            hm1 = start.hour * 100 + start.minute
            while hm1 < hm:
                start = start + delta
                hm1 = start.hour * 100 + start.minute
            return start
        elif hm == 1130:
            return now
        elif hm >= 1500:
            return now.replace(hour=15, minute=0, second=0)

def getNextPeriodDatetime(period: Period, dt: datetime.datetime):
    delta = datetime.timedelta(days=1)
    if period == Period.minute30:
        delta = datetime.timedelta(minutes=30)
    elif period == Period.minute5:
        delta = datetime.timedelta(minutes=5)
    elif period == Period.minute1:
        delta = datetime.timedelta(minutes=1)
    elif period == Period.week:
        delta = datetime.timedelta(weeks=1)
    dtRel = dt
    hms = dtRel.hour * 10000 + dtRel.minute * 100 + dtRel.second
    if 93000 <= hms < 113000 or 130000 <= hms < 150000:
        dtRel = dtRel + delta
        return dtRel
    elif hms == 113000:
        dtRel = dtRel.replace(hour=13, minute=0)
        dtRel = dtRel + delta
        return dtRel
    elif hms == 150000:
        dtRel = dtRel + datetime.timedelta(days=1)
        dtRel = dtRel.replace(hour=9, minute=30)
        dtRel = dtRel + delta
        return dtRel


def getNextTradingDay(date: datetime.date) -> datetime.date:
    """
    获取下一个交易日
    :param date:
    :return:
    """
    oneday = datetime.timedelta(days=1)
    d = date + oneday
    while not is_trading_day(d.strftime(DateFormat.Y_m_d.value)):
        d = d + oneday
    return d


def get_timestamp_from_date(date: str, type="start"):
    """
    根据日期字符串获取时间戳
    :param date: format：YYYY-MM-DD ,如果是为None或"",则表示为当前日期
    :param type:
        start 为根据指定日期最早时间生成时间戳(也就是指定日期的"00:00:00"的时间)
        end 为根据指定日期最晚时间生成时间戳(也就是指定日期的"23:59:59"的时间)
    :return: float 示例:1512662400.0
    """
    date = str(today()) if date == None or date == "" else date
    date = date + " 00:00:00" if type == "start" else date + " 23:59:59"
    return get_timestamp_from_datetimeStr(date, DateFormat.Y_m_d_H_M_S)


def get_long_timestamp_from_dateStr(date: str, type="start"):
    """
    根据日期字符串获取时间戳
    :param date: format：YYYY-MM-DD ,如果是为None或"",则表示为当前日期
    :param type:
        start 为根据指定日期最早时间生成时间戳(也就是指定日期的"00:00:00"的时间)
        end 为根据指定日期最晚时间生成时间戳(也就是指定日期的"23:59:59"的时间)
    :return: float 示例:1512662400.0
    """
    date = str(today()) if date == None or date == "" else date
    date = date + " 00:00:00" if type == "start" else date + " 23:59:59"
    # 转换成时间戳
    timestamp = get_timestamp_from_datetimeStr(date, DateFormat.Y_m_d_H_M_S)
    return int(timestamp * 1000)


def get_timestamp_from_datetimeStr(datetimeStr:str, dateformat: DateFormat=DateFormat.Y_m_d_H_M_S):
    """
    根据指定格式的时间字符串获取时间戳
    :param datetimeStr:
    :param dateformat:
    :return:
    """
    # 转换成时间数组
    timeArray = time.strptime(datetimeStr, dateformat.value)
    # 转换成时间戳
    return time.mktime(timeArray)


def get_timeStr_from_timestamp(timestamp: float, pattern: DateFormat=DateFormat.Y_m_d_H_M_S):
    """
    根据时间戳获取时间字符串,没有考虑时区
    :param timestamp:float 示例:1512662400.0
    :return:str format:YYYY-MM-DD HH:MM:SS
    """
    time = get_datetime_from_timestamp(timestamp)
    return time.strftime(pattern.value)


def get_dateStr_from_timestamp(timestamp: float, pattern: DateFormat=DateFormat.Y_m_d_H_M_S):
    """
    根据时间戳获取时间字符串,没有考虑时区
    :param timestamp:float 示例:1512662400.0
    :return:str format:YYYY-MM-DD
    """
    time = get_datetime_from_timestamp(timestamp)
    return time.strftime(pattern.value)

def get_datetime_from_timestamp(timestamp: float):
    """
    根据时间戳获取时间
    :param timestamp:
    :return:
    """
    if (len(str(int(timestamp))) == 13):
        timestamp = timestamp / 1000
    return datetime.datetime.fromtimestamp(timestamp)

def get_datetime_from_timestamp(timestamp: float):
    if (len(str(int(timestamp))) == 13):
        timestamp = timestamp / 1000
    return datetime.datetime.fromtimestamp(timestamp)


def get_month_FirstDay_LastDay(year=None, month=None):
    """
    获取指定月份的开始日期和结束日期
    :param year: 年份，默认是本年，可传int或str类型
    :param month: 月份，默认是本月，可传int或str类型
    :return: firstDay: 当月的第一天，datetime.date类型
              lastDay: 当月的最后一天，datetime.date类型
    """
    year = int(year) if year else get_current_year()
    month = int(month) if month else get_current_month()

    import calendar
    # 获取当月第一天的星期和当月的总天数
    firstDayWeekDay, monthRange = calendar.monthrange(year, month)

    # 获取当月的第一天
    firstDay = datetime.date(year=year, month=month, day=1)
    lastDay = datetime.date(year=year, month=month, day=monthRange)

    return firstDay, lastDay


def change_datatime(time, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0):
    """
    得到指定间隔的时间，所有的参数都有默认值0，这些参数可以是int或float，正的或负的。
    :param time:
    :param days:
    :param seconds:
    :param microseconds:
    :param milliseconds:
    :param minutes:
    :param hours:
    :param weeks:
    :return:
    """
    delta = datetime.timedelta(days=days, seconds=seconds, microseconds=microseconds, milliseconds=milliseconds,
                               minutes=minutes, hours=hours, weeks=weeks)
    return time + delta


def get_year_cut_off(year=None, type="start"):
    """
    获取指定年份的开始和结束日期
    :param year:可以是int,str和None类型
    :param type:str 默认为start,为指定年份的开始日期.end为指定年份的结束日期
    :return:str format：YYYY-MM-DD
    """
    year = get_current_year() if year == None or year == 0 or year == "" else year
    if type == "start":
        return str(year) + "-01-01"
    else:
        return str(year) + "-12-31"


def datetime64_to_datetime(dt64):
    return pd.to_datetime(dt64)


def datetime_to_dateStr(date, pattern: DateFormat=DateFormat.Y_m_d_H_M_S):
    return date.strftime(pattern.value)


def generateExchangeClosedDates(year: int, closedDate: list):
    """
    生成股票休市日期列表
    :param year: 年份
    :param closedDate: 为不包含所有周六日的股市休市安排，一般为国家规定的休假日期列表 例如：["2024-01-01", "2024-02-10", "2024-02-11", "2024-02-12", "2024-02-13", "2024-02-14", "2024-02-15", "2024-02-16", "2024-02-17", "2024-04-04", "2024-04-05", "2024-04-06", "2024-05-01", "2024-05-02", "2024-05-03", "2024-05-04", "2024-05-05", "2024-06-10", "2024-09-15", "2024-09-16", "2024-09-17", "2024-10-01", "2024-10-02", "2024-10-03", "2024-10-04", "2024-10-05", "2024-10-06", "2024-10-07"]
    :return:
    """
    dateList = pd.date_range(start=str(year) + "-1-1", end=str(year)+"-12-31")
    for d in dateList.tolist():
        dd = datetime.datetime.strftime(d, '%Y-%m-%d')
        if d.day_of_week in [5, 6] or dd in closedDate:
            print(f"{dd},1,1")





if __name__ == "__main__":
    is_trading_day(date="2024-05-05")
